﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using PgBirdie.Core.Config;
using PgBirdie.Core.Helpers;
using PgBirdie.Core.Model.Database.Structure;
using PgBirdie.Core.Service.DbObject;
using PgBirdie.Core.Service.Infrastructure;

namespace PgBirdie.Core.Service.Dump
{
    public class DumpToDiskService : IDumpToDiskService
    {
        private readonly IDbObjectFromDbService _dbObjectFromDb;
        private readonly IDbObjectToSqlService _dbObjectToSql;        
        private readonly IFileSystemService _fileSystem;

        public DumpToDiskService(IDbObjectFromDbService dbObjectFromDb, IDbObjectToSqlService dbObjectToSql, IFileSystemService fileSystem)
        {
            _dbObjectFromDb = dbObjectFromDb;
            _dbObjectToSql = dbObjectToSql;
            _fileSystem = fileSystem;
        }

        public bool CreateDatabaseDirectory(PgDatabase db)
        {
            var path = String.Format(@"{0}{1}", Settings.PathTo.Dump, db.Name);
            if (!Directory.Exists(path))
                Directory.CreateDirectory(path);
            return true;
        }

        public bool CreateSchemaDirectory(PgSchema schema)
        {
            var path = String.Format(@"{0}{1}\{2}", Settings.PathTo.Dump, schema.Database.Name, schema.Name);
            if (!Directory.Exists(path))
                Directory.CreateDirectory(path);
            return true;
        }

        public bool CreateTableStructureDirectory(PgSchema schema)
        {
            var path = String.Format(@"{0}{1}\{2}\{3}", Settings.PathTo.Dump, schema.Database.Name, schema.Name,
                                     Settings.Misc.DirNameForTables);
            if (!Directory.Exists(path))
                Directory.CreateDirectory(path);
            return true;
        }

        public bool CreateTableStaticDataDirectory(PgSchema schema)
        {
            var path = String.Format(@"{0}{1}\{2}\{3}", Settings.PathTo.Dump, schema.Database.Name, schema.Name,
                                     Settings.Misc.DirNameForStaticData);
            if (!Directory.Exists(path))
                Directory.CreateDirectory(path);
            return true;
        }

        public bool CreateSequenceDirectory(PgSchema schema)
        {
            var path = String.Format(@"{0}{1}\{2}\{3}", Settings.PathTo.Dump, schema.Database.Name, schema.Name,
                         Settings.Misc.DirNameForSequences);
            if (!Directory.Exists(path))
                Directory.CreateDirectory(path);
            return true;
        }

        public bool ToDisk(string path, string content)
        {
            return _fileSystem.CreateFile(path, content, true);
        }

        public bool PurgeDatabaseDirectory(PgDatabase db)
        {
            var path = String.Format(@"{0}{1}", Settings.PathTo.Dump, db.Name);

            if (Directory.Exists(path))
                try
                {
                    _fileSystem.DeleteDirectory(path);
                }
                catch (IOException)
                {
                    AppConsole.WriteLine("Could not purge database folder. Please check that you're not locking " +
                                         String.Format("the folder path: {1}.{0}", Environment.NewLine, path));
                    return false;
                }
            return true;
        }

        public string GetDatabaseStructureSql(PgDatabase db)
        {
            var sql = new StringBuilder();

            sql.Append(_dbObjectToSql.CreateDatabase(db));
            sql.Append(_dbObjectToSql.SetOwner(db.Owner));
            foreach (var priv in db.Privileges.Values.OrderByDescending(c => c.CommandType))
                sql.Append(_dbObjectToSql.SetPrivilege(priv));

            return sql.ToString();
        }

        public string GetSchemaStructureSql(PgSchema schema)
        {
            var sql = new StringBuilder();

            sql.Append(_dbObjectToSql.CreateSchema(schema));
            sql.Append(_dbObjectToSql.SetOwner(schema.Owner));
            foreach (var priv in schema.Privileges.Values.OrderByDescending(c => c.CommandType))
                sql.Append(_dbObjectToSql.SetPrivilege(priv));

            return sql.ToString();
        }

        public string GetSequenceSql(PgSequence sequence)
        {
            var sql = new StringBuilder();

            sql.Append(_dbObjectToSql.CreateSequence(sequence));

            sql.Append(_dbObjectToSql.SetOwner(sequence.Owner));

            foreach (var priv in sequence.Privileges.Values.OrderByDescending(c => c.CommandType))
                sql.Append(_dbObjectToSql.SetPrivilege(priv));

            return sql.ToString();
        }

        public string GetTableStructureSql(PgTable table)
        {
            var sql = new StringBuilder();

            sql.Append(_dbObjectToSql.CreateTable(table));

            foreach (var column in table.Columns.Values)
                sql.Append(_dbObjectToSql.AssociateTableColumnWithSequence(column));

            foreach (var index in table.Indexes.Values)
                sql.Append(_dbObjectToSql.CreateIndex(index));

            sql.Append(_dbObjectToSql.AddPrimaryKey(table.PrimaryKey));

            foreach (var key in table.UniqueKeys.Values)
                sql.Append(_dbObjectToSql.AddUniqueKey(key));

            foreach (var key in table.ForeignKeys.Values)
                sql.Append(_dbObjectToSql.AddForeignKey(key));

            foreach (var check in table.Checks.Values)
                sql.Append(_dbObjectToSql.AddCheck(check));

            sql.Append(_dbObjectToSql.SetOwner(table.Owner));

            foreach (var priv in table.Privileges.Values.OrderByDescending(c => c.CommandType))
                sql.Append(_dbObjectToSql.SetPrivilege(priv));

            return sql.ToString();
        }

        public string GetTableStaticDataSql(PgTable table)
        {
            var sql = new StringBuilder();

            foreach (var dataRow in table.DataRows.Values)
            {
                sql.AppendFormat("INSERT INTO \"{0}\" VALUES (", table.Name);

                var values = new List<string>();
                foreach (var dataColumn in dataRow.DataColumns.Values)
                    values.Add(String.Format("{0}", dataColumn.Value));

                sql.Append(String.Join(", ", values.ToArray()));

                sql.AppendFormat(");{0}", Environment.NewLine);
            }

            return sql.ToString();
        }

        public string GetResetSql(PgDatabase db)
        {
            var sb = new StringBuilder("BEGIN;" + Environment.NewLine);

            var settingTables = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
            foreach (var configSchema in Settings.Source.Schemas.Values)
                foreach (var configTable in configSchema.Tables.Values)
                    settingTables.Add(String.Format("{0}.{1}", configSchema.Name, configTable.Name), configTable.WithData);

            var tables = _dbObjectFromDb.GetAllTables(db);

            var tablesToReset = new List<PgTable>();

            foreach (var table in tables.Values.OrderByDescending(x => x.OrderNo))
            {
                var key = String.Format("{0}.{1}", table.Schema.Name, table.Name);
                if (settingTables.ContainsKey(key) && !settingTables[key])
                    tablesToReset.Add(table);
            }

            sb.Append(String.Join(Environment.NewLine, tablesToReset.Select(x => String.Format("DELETE FROM \"{0}\".\"{1}\";",x.Schema.Name, x.Name))));

            sb.Append(Environment.NewLine);

            sb.Append(String.Join(Environment.NewLine, tablesToReset.Select(x => String.Format("TRUNCATE \"{0}\".\"{1}\" CASCADE;", x.Schema.Name, x.Name))));

            sb.AppendFormat("{0}COMMIT;", Environment.NewLine);

            return sb.ToString();
        }
    }
}